﻿/*----------------------------------------------------------------
//  <copyright file="UserClaimsProvider.cs" company="MicroCloud@151504200868">
//      Copyright © 2020-2024 MicroCloud Corporation, All rights reserved.
//  </copyright>
//  <site>https://gitee.com/chenmm123/microclouds</site>
//  <last-editor>cmm</last-editor>
//  <last-date>2023-12-28 11:32</last-date>
//----------------------------------------------------------------*/

namespace MicroCloud.Authentication
{
    #region "用户 Claims 提供器"
    /// <summary>
    /// 用户 Claims 提供器
    /// </summary>
    /// <typeparam name="TUser">用户类型</typeparam>
    /// <typeparam name="TUserKey">用户编号类型</typeparam>
    /// <typeparam name="TRole">角色类型</typeparam>
    /// <typeparam name="TRoleKey">角色编号类型</typeparam>
    /// <typeparam name="TOrganization">组织机构类型</typeparam>
    /// <typeparam name="TOrganizationKey">组织机构编号类型</typeparam>
    public class UserClaimsProvider<TUser, TUserKey, TRole, TRoleKey, TOrganization, TOrganizationKey> : IUserClaimsProvider
        where TUser : UserBase<TUserKey>
        where TUserKey : struct, IEquatable<TUserKey>
        where TRole : RoleBase<TRoleKey, TUserKey>
        where TRoleKey : struct, IEquatable<TRoleKey>
        where TOrganization : OrganizationBase<TOrganizationKey, TUserKey>
        where TOrganizationKey : struct, IEquatable<TOrganizationKey>
    {
        //字段
        private readonly IServiceProvider _provider;

        #region "构造函数"
        #region "初始化一个用户 Claims 提供器的新实例"
        /// <summary>
        /// 初始化一个用户 Claims 提供器 <see cref="UserClaimsProvider{TUser, TUserKey, TRole, TRoleKey, TOrganization, TOrganizationKey}"/> 的新实例
        /// </summary>
        /// <param name="provider"></param>
        public UserClaimsProvider(IServiceProvider provider)
        {
            _provider = provider;
        }
        #endregion

        #endregion

        #region "方法"
        #region "创建用户标识"
        /// <summary>
        /// 创建用户标识
        /// </summary>
        /// <param name="userId">用户编号</param>
        /// <returns></returns>
        public async Task<Claim[]> CreateClaims(string userId)
        {
            Check.NotNullOrEmpty(userId, nameof(userId));

            UserManager<TUser> userManager = _provider.GetService<UserManager<TUser>>();
            TUser user = await userManager.FindByIdAsync(userId);
            if (user == null)
            {
                throw new Exception(I18N.T("用户 {0} 不存在", userId));
            }

            Claim[] claims =
            {
                new(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new(ClaimTypes.Name, user.UserName)
            };
            return claims;
        }
        #endregion
        #region "处理用户当前组织机构声明"
        /// <summary>
        /// 处理用户当前组织机构声明
        /// </summary>
        /// <param name="claims">声明集合</param>
        /// <param name="organizationId">组织机构编号</param>
        /// <returns></returns>
        public async Task<Claim[]> HandleCurrentOrganizationClaims(IEnumerable<Claim> claims, string organizationId = "")
        {
            Check.NotNull(claims, nameof(claims));

            string userId = claims.FirstOrDefault(m => m.Type == ClaimTypes.NameIdentifier)?.Value;
            if (userId == null)
            {
                throw new Exception(I18N.T("{0} 声明不存在", "NameIdentifier"));
            }
            var onlineUserProvider = _provider.GetService<IOnlineUserProvider>();
            var onlineUser = await onlineUserProvider.GetOrCreate(userId);
            if (onlineUser == null)
            {
                throw new Exception(I18N.T("用户 {0} 不存在", userId));
            }
            //如果传入的组织结构名称为空且用户只有一个可用组织机构信息，则默认
            if (organizationId.IsMissing())
            {
                //处理用户组织机构
                var organizationProvider = _provider.GetService<IOrganizationProvider>();
                var organizationDtos = organizationProvider.GetUserOrganizations(onlineUser.UserId).Result.Where(o => !o.IsLocked).ToList();

                if (organizationDtos.Count == 1)
                {
                    organizationId = organizationDtos.FirstOrDefault()?.Id;
                }
            }
            //如果组织机构名称为空，则直接返回传入声明
            if (organizationId.IsMissing())
            {
                return claims.ToArray();
            }

            var claimsList = claims.ToList();
            //重新登记组织机构信息
            claimsList = SetCurrentOrganizationClaim(claimsList, onlineUser, organizationId);

            return claimsList.ToArray();
        }
        #endregion
        #region "请求的Token验证成功后使用OnlineUser信息刷新Identity，将在线用户信息赋予Identity"
        /// <summary>
        /// 请求的Token验证成功后使用OnlineUser信息刷新Identity，将在线用户信息赋予Identity
        /// </summary>
        /// <param name="identity">待刷新的Identity</param>
        /// <returns>刷新后的Identity</returns>
        public async Task<OperationResult<ClaimsIdentity>> RefreshIdentity(ClaimsIdentity identity)
        {
            if (identity != null && identity.IsAuthenticated)
            {
                var userId = identity.GetUserId();
                var onlineUserProvider = _provider.GetService<IOnlineUserProvider>();
                var onlineUser = await onlineUserProvider.GetOrCreate(userId);
                if (onlineUser == null)
                {
                    return OperationResult.Error<ClaimsIdentity>(I18N.T("用户 {0} 不存在", userId));
                }

                string clientId = identity.GetClaimValueFirstOrDefault("clientId");
                if (clientId != null && onlineUser.RefreshTokens.All(m => m.Value.ClientId != clientId))
                {
                    return OperationResult.Error<ClaimsIdentity>(I18N.T("当前客户端的 RefreshToken 已过期"));
                }

                //删除已登记信息
                identity.RemoveClaims(ClaimTypes.GivenName);
                identity.RemoveClaims(ClaimTypes.Email);
                identity.RemoveClaims(ClaimTypes.MobilePhone);
                identity.RemoveClaims(ClaimTypes.Role);
                //重新登记信息
                if (!onlineUser.NickName.IsMissing()) { identity.AddClaim(new Claim(ClaimTypes.GivenName, onlineUser.NickName)); }
                if (!onlineUser.Email.IsMissing()) { identity.AddClaim(new Claim(ClaimTypes.Email, onlineUser.Email)); }
                if (!onlineUser.PhoneNumber.IsMissing()) { identity.AddClaim(new Claim(ClaimTypes.MobilePhone, onlineUser.PhoneNumber)); }
                //设置用户所拥有的角色集合
                //if (onlineUser.Roles.Length > 0) { identity.AddClaim(new Claim(ClaimTypes.Role, onlineUser.Roles.ExpandAndToString(o => o.Name))); }
                if (onlineUser.Roles.Length > 0) { identity.AddClaim(new Claim(ClaimTypes.Role, onlineUser.Roles.ToJsonString())); }

                // 扩展数据
                foreach (KeyValuePair<string, string> pair in onlineUser.ExtendData)
                {
                    identity.AddClaim(new Claim(pair.Key, pair.Value));
                }
            }

            ScopedDictionary dict = _provider.GetService<ScopedDictionary>();
            if (dict != null)
            {
                dict.Identity = identity;
            }

            return OperationResult.Success(identity);
        }
        #endregion

        #endregion

        #region "私有方法"
        #region "设置用户当前组织机构及部门机构申明"
        /// <summary>
        /// 设置用户当前组织机构及部门机构申明
        /// </summary>
        /// <param name="claims"></param>
        /// <param name="onlineUser"></param>
        /// <param name="currentOrganizationId"></param>
        /// <returns></returns>
        private List<Claim> SetCurrentOrganizationClaim(List<Claim> claims, OnlineUser onlineUser, string currentOrganizationId = "")
        {
            //删除已登记组织机构信息
            claims.RemoveAll(o => o.Type.Equals(ClaimTypeConstants.CurrentOrganizationId));
            claims.RemoveAll(o => o.Type.Equals(ClaimTypeConstants.CurrentDepartmentId));
            if (currentOrganizationId.IsMissing())
            {
                return claims;
            }
            if (onlineUser == null)
            {
                return claims;
            }
            //验证登录的组织机构信息
            var queryableOrganizationStore = _provider.GetService<IQueryableOrganizationStore<TOrganization>>();
            var organizationKey = currentOrganizationId.CastTo<TOrganizationKey>(default);
            var organizationEntity = queryableOrganizationStore.Organizations.FirstOrDefault(o => Equals(o.Id, organizationKey) && !o.IsLocked);
            if (organizationEntity == null)
            {
                throw new Exception(I18N.T("组织机构 {0} 不存在或已锁定", currentOrganizationId));
            }
            var organizationProvider = _provider.GetService<IOrganizationProvider>();
            var organizationDtos = organizationProvider.GetUserOrganizations(onlineUser.UserId).Result;
            if (!organizationDtos.Any(o => Equals(o.Id, currentOrganizationId) && !o.IsLocked))
            {
                throw new Exception(I18N.T("用户 {0} 不被允许设定组织机构 {1}", onlineUser.UserName, organizationEntity.Name));
            }
            if (organizationEntity.Type == OrganizationType.Department)
            {
                claims.Add(new Claim(ClaimTypeConstants.CurrentDepartmentId, organizationEntity.Id.ToString()));

                var parentOrg = queryableOrganizationStore.Organizations.FirstOrDefault(m => Equals(m.Id, organizationEntity.ParentId));
                while (parentOrg != null)
                {
                    if (parentOrg.EnableLogin && (parentOrg.Type == OrganizationType.Virtual || parentOrg.Type == OrganizationType.Corporation))
                    {
                        if (parentOrg.IsLocked)
                        {
                            throw new Exception(I18N.T("组织机构 {0} 的父级 {1} 已锁定", organizationEntity.Name, parentOrg.Name));
                        }
                        claims.Add(new Claim(ClaimTypeConstants.CurrentOrganizationId, parentOrg.Id.ToString()));
                        break;
                    }
                    parentOrg = queryableOrganizationStore.Organizations.FirstOrDefault(m => Equals(m.Id, parentOrg.ParentId));
                }
            }
            else
            {
                claims.Add(new Claim(ClaimTypeConstants.CurrentOrganizationId, organizationEntity.Id.ToString()));
            }

            return claims;
        }
        #endregion

        #endregion

    }
    #endregion

}
